#include "backup_server_connection.h"

#include <iostream>
#include <sstream>

backup_server_connection::backup_server_connection(const std::string& address,
                                                   const std::string& port,
                                                   const std::string& file,
                                                   size_t max_simultaneous_downloads)
    : work(io_service)
{
    std::stringstream ss;
    ss << address << ":" << port << "/" << file;

    connection_string = ss.str();


    for(size_t i = 0; i < max_simultaneous_downloads; ++i)
    {
        boost::shared_ptr<boost::thread> thread(
            new boost::thread(
                boost::bind(
                    &boost::asio::io_service::run, &io_service)));
        threads.push_back(thread);
    }

}

backup_server_connection::~backup_server_connection()
{

#ifdef DEBUG
    std::cerr << "backup_server_connection: "
              << "waiting for worker threads to terminate..."
              << std::endl;
#endif
  
    io_service.stop();

    // wait for threads to complete their tasks
    for(size_t i = 0; i < threads.size(); ++i)
    {
        threads[i]->join();
    }
    
}

void backup_server_connection::send(const request& req,
                                    response_handler_fun response_handler)
{
    io_service.post(
        boost::bind(&backup_server_connection::worker, 
                    this,
                    connection_string, 
                    req,
                    response_handler));
}   

void backup_server_connection::worker(std::string connection_str,
                                      request req,
                                      response_handler_fun response_handler)
{
    
    // create a new curl instance for this thread if none exists
    if(curl_ptr.get() == 0)
    {
        curl_ptr.reset(new curl_instance(connection_str));
        // please note that the curl_instance will be deleted automatically 
        // when this thread terminates.
    }

    /* allocate a fat buffer for all pieces to be downloaded.
     * buffer_wrapper will delete its internal buffer when we fall out 
     * of this scope.
     */
    buffer_wrapper wrapper(req.piece_size * req.count);

    // these are the pointers we will actually pass to the user
    std::vector<char*> piece_buffers;
    
    // "slice" up the buffer into pieces
    // (in fact, we just do some pointer arithmetic on the buffer)
    for(size_t i = 0; i < req.count; ++i)
    {
        piece_buffers.push_back(
            wrapper.buffer + (req.piece_size * i));
    }

    curl_easy_setopt(curl_ptr->curl, CURLOPT_WRITEDATA, (void*)&wrapper); 
            
    std::stringstream size_str;
    size_str << "Size: " << req.piece_size;

    std::stringstream index_str;
    index_str << "Index: " << req.index;

    std::stringstream count_str;
    count_str << "Count: " << req.count;

    struct curl_slist *chunk = NULL;
    chunk = curl_slist_append(chunk, size_str.str().c_str());
    chunk = curl_slist_append(chunk, index_str.str().c_str());
    chunk = curl_slist_append(chunk, count_str.str().c_str());

    CURLcode res;
    
    res = curl_easy_setopt(curl_ptr->curl, CURLOPT_HTTPHEADER, chunk);
    
    if(res != CURLE_OK) {
#ifdef DEBUG
        std::cerr << "backup_server_connection: error: " 
                  << curl_easy_strerror(res) << std::endl;
#endif
        return;
    }
    
    res = curl_easy_perform(curl_ptr->curl);
    
    if(res != CURLE_OK) {
#ifdef DEBUG
        std::cerr << "backup_server_connection: error: " 
                  << curl_easy_strerror(res) << std::endl;
#endif
        return;
    }
    
    // invoke user defined callback
    response_handler(req, piece_buffers);

}

size_t backup_server_connection::curl_instance::write_data(void * buffer, 
                                                           size_t size, 
                                                           size_t nmemb, 
                                                           void *userp)
{
    if(size != 1) {
#ifdef DEBUG
        std::cout << "backup_server_connection: error: "
                  << "chunk size not 1" << std::endl;
#endif
        return 0;
    }

    buffer_wrapper* wrapper = (buffer_wrapper*)userp;

    if(wrapper->bytes_written + nmemb > wrapper->buf_size) 
    {
#ifdef DEBUG
        std::cout << "backup_server_connection: error: "
                  << "buffer overflow" << std::endl;
#endif
        return 0;
    }

    for(size_t i = 0; i < nmemb; ++i) {
        wrapper->buffer[wrapper->bytes_written++] = ((char*)buffer)[i];
    }

    return size * nmemb;
}

